Skip to content

feat(zoo-gateway): provider types, handler, and model fetcher#344

Open
JamesRobert20 wants to merge 7 commits into
mainfrom
feat/zoo-gateway-types-core
Open

feat(zoo-gateway): provider types, handler, and model fetcher#344
JamesRobert20 wants to merge 7 commits into
mainfrom
feat/zoo-gateway-types-core

Conversation

@JamesRobert20
Copy link
Copy Markdown
Contributor

@JamesRobert20 JamesRobert20 commented May 27, 2026

Summary

  • Adds Zoo Gateway provider types, OpenAI-compatible handler, and authenticated model fetcher
  • Skips model cache reads/writes for zoo-gateway (auth-scoped model lists)
  • Adds request timeout and safe error logging on model discovery

Part 1 of the Zoo Gateway stack (split from #229).

Test plan

  • Unit tests: modelCache, zoo-gateway fetcher
  • After stack merges: Zoo Gateway model list loads when signed in

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added Zoo Gateway as a supported API provider with dynamic model discovery, UI model selection, a preconfigured Anthropic default model and default temperature, session-token authentication, streaming and single-request completions, and optional prompt-caching for eligible models.
  • Behavior

    • Treats certain providers as Anthropic-style when model IDs start with anthropic/. Model discovery for Zoo Gateway skips local caches and exposes an initially empty model list for remote population.
  • Tests

    • Added unit tests for model discovery, parsing, streaming, error handling, and token/log redaction.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new dynamic provider "zoo-gateway": provider types and defaults, a /models fetcher with auth and Zod validation, cache/refresh logic that skips persistent caching for per-user lists, a ZooGatewayHandler for streaming and non-streaming completions, and UI wiring/tests.

Changes

Zoo Gateway Provider Integration

Layer / File(s) Summary
Provider configuration & types
packages/types/src/provider-settings.ts, packages/types/src/providers/zoo-gateway.ts, packages/types/src/providers/index.ts, src/shared/api.ts
Add zoo-gateway to dynamicProviders and provider settings schema; introduce zooGatewayModelId, protocol routing for anthropic/*, default model/temperature and exports, and update GetModelsOptions typing.
Model discovery & auth-scoped caching
src/api/providers/fetchers/zoo-gateway.ts, src/api/providers/fetchers/modelCache.ts, src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts
Implement /models discovery with optional Bearer auth, Zod response validation, convert to shared ModelInfo, and add model-cache logic that skips persistent cache reads/writes and treats refresh/failure as returning {} for zoo-gateway. Includes tests for auth, timeout, validation, and delegation.
API handler implementation
src/api/providers/zoo-gateway.ts, src/api/index.ts, src/api/providers/index.ts
Add ZooGatewayHandler enforcing session token, enrich OpenAI client headers, stream chat completions (Anthropic→OpenAI conversion, prompt caching), emit text/tool chunks and detailed usage with cache tokens/cost, implement non-streaming completePrompt, and wire handler into buildApiHandler and re-export.
Webview & tests wiring
src/core/webview/webviewMessageHandler.ts, webview-ui/src/components/ui/hooks/useSelectedModel.ts, webview-ui/src/utils/__tests__/validate.spec.ts, src/core/webview/__tests__/*, src/api/providers/__tests__/zoo-gateway.spec.ts
Include zoo-gateway in routerModels default, select models using apiConfiguration.zooGatewayModelId against routerModels, update test fixtures and webview tests to expect zoo-gateway entries, and add comprehensive handler tests.

Sequence Diagram(s)

sequenceDiagram
  participant Requestor
  participant ModelCache
  participant ZooFetcher
  participant ZooAPI
  Requestor->>ModelCache: getModels(zoo-gateway, apiKey?, baseUrl?)
  ModelCache-->>ZooFetcher: fetchModelsFromProvider(zoo-gateway)
  ZooFetcher->>ZooAPI: GET /models (timeout, optional Bearer)
  ZooAPI-->>ZooFetcher: models[]
  ZooFetcher->>ModelCache: parsed ModelInfo (no persistent write)
  ModelCache-->>Requestor: models map
Loading
sequenceDiagram
  participant Client
  participant ZooGatewayHandler
  participant OpenAIClient
  participant ZooAPI
  Client->>ZooGatewayHandler: createMessage(system, messages, metadata)
  ZooGatewayHandler->>OpenAIClient: stream chat completion (converted messages, headers)
  OpenAIClient->>ZooAPI: POST /chat/completions (stream)
  ZooAPI-->>OpenAIClient: stream events (deltas, usage)
  OpenAIClient-->>ZooGatewayHandler: events
  ZooGatewayHandler-->>Client: text chunks, tool-call chunks, usage (tokens, cache fields, cost)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • taltas
  • navedmerchant
  • hannesrudolph
  • edelauna

Poem

🐰 From burrow bright a gateway springs,
Tokens hum and model sings,
Bearer keys across the stream,
Claude and cache in sync, a dream,
Rabbit cheers: "New provider, new things!"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete relative to the template. It lacks the required 'Related GitHub Issue' section, missing issue link, and incomplete test procedure and checklist sections. Add the missing 'Related GitHub Issue' section with the issue number, complete the 'Test Procedure' section with detailed steps, and check off the pre-submission checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding Zoo Gateway provider types, handler, and model fetcher.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/zoo-gateway-types-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/providers/fetchers/modelCache.ts (1)

174-185: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not share in-flight refresh promises for auth-scoped Zoo Gateway models.

Line 182 deduplicates by provider only. For zoo-gateway, concurrent calls from different auth sessions can receive the same in-flight result, leaking cross-user model visibility.

Proposed fix
 	const shouldSkipCache = provider === "zoo-gateway"
+	const canShareInFlight = !shouldSkipCache

-	const existingRequest = inFlightRefresh.get(provider)
-	if (existingRequest) {
-		return existingRequest
+	if (canShareInFlight) {
+		const existingRequest = inFlightRefresh.get(provider)
+		if (existingRequest) {
+			return existingRequest
+		}
 	}
@@
-	inFlightRefresh.set(provider, refreshPromise)
+	if (canShareInFlight) {
+		inFlightRefresh.set(provider, refreshPromise)
+	}

Also applies to: 237-239

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/fetchers/modelCache.ts` around lines 174 - 185, The current
inFlightRefresh dedupes only by provider (inFlightRefresh.get(provider)), which
lets different authenticated sessions share the same in-flight promise for the
auth-scoped "zoo-gateway" provider; change the dedupe key to include an
auth-scoped identifier when shouldSkipCache is true. Concretely, compute a
cacheKey = shouldSkipCache ? `${provider}:${authIdOrToken}` : provider (use the
request's existing auth/session identifier already available to getModels —
e.g., sessionId, userId, or authToken), then replace uses of
inFlightRefresh.get(provider)/set(provider, promise)/delete(provider) with
inFlightRefresh.get(cacheKey)/set(cacheKey,...)/delete(cacheKey). Apply this
same change at both places noted (the existing block you saw and the 237-239
block) so in-flight promises for "zoo-gateway" are scoped per-auth.
🧹 Nitpick comments (2)
src/api/providers/zoo-gateway.ts (2)

61-70: ⚖️ Poor tradeoff

Consider passing headers to parent instead of recreating client.

Overriding client via (this as any) bypasses type safety and creates the OpenAI client twice. If RouterProvider doesn't support custom headers in its constructor options, consider extending it to accept a defaultHeaders parameter, which would avoid the double instantiation and the unsafe cast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/zoo-gateway.ts` around lines 61 - 70, The code recreates
the OpenAI client via (this as any).client = new OpenAI(...) which bypasses
types and double-instantiates the client; instead extend RouterProvider to
accept a defaultHeaders (or openAiOptions) parameter and pass enrichmentHeaders
and options.zooSessionToken into the parent constructor so the existing client
instance is created with the correct headers; remove the unsafe cast and the new
OpenAI(...) call in ZooGateway (or the class containing this.client) and update
the parent constructor signature (RouterProvider) to merge DEFAULT_HEADERS,
enrichmentHeaders, and options.openAiHeaders when constructing the OpenAI
client.

109-111: ⚡ Quick win

Set tool-related parameters only when tools are provided.

When tools is undefined, tool_choice and parallel_tool_calls should also be omitted. Some OpenAI-compatible APIs may error or behave unexpectedly when these parameters are present without tools.

♻️ Proposed fix
 		const body: OpenAI.Chat.ChatCompletionCreateParams = {
 			model: modelId,
 			messages: openAiMessages,
 			temperature: this.supportsTemperature(modelId)
 				? (this.options.modelTemperature ?? ZOO_GATEWAY_DEFAULT_TEMPERATURE)
 				: undefined,
 			max_completion_tokens: info.maxTokens,
 			stream: true,
 			stream_options: { include_usage: true },
-			tools: this.convertToolsForOpenAI(metadata?.tools),
-			tool_choice: metadata?.tool_choice,
-			parallel_tool_calls: metadata?.parallelToolCalls ?? true,
+			...(metadata?.tools && {
+				tools: this.convertToolsForOpenAI(metadata.tools),
+				tool_choice: metadata.tool_choice,
+				parallel_tool_calls: metadata.parallelToolCalls ?? true,
+			}),
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/zoo-gateway.ts` around lines 109 - 111, When building the
request payload in zoo-gateway.ts (around the object using
convertToolsForOpenAI), only include tool-related keys when metadata?.tools is
present: call convertToolsForOpenAI(metadata.tools) and, if that returns a
non-empty array, set tools, tool_choice (from metadata?.tool_choice) and
parallel_tool_calls (from metadata?.parallelToolCalls ?? true); otherwise omit
tool_choice and parallel_tool_calls entirely. Update the payload construction
logic to conditionally add these properties instead of unconditionally setting
tool_choice and parallel_tool_calls when tools are undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/fetchers/zoo-gateway.ts`:
- Around line 77-79: The code currently falls back to unvalidated
response.data.data after zooGatewayModelsResponseSchema.safeParse fails; change
this to “fail closed” by not using response.data when result.success is false —
either throw or set data to a safe default (e.g., empty array/object) and log
the validation issues from result.error; update the assignment that creates data
(the block that uses zooGatewayModelsResponseSchema.safeParse, result, and
response) so callers only ever iterate/consume validated data (or the empty
default) instead of untrusted response.data.data.

---

Outside diff comments:
In `@src/api/providers/fetchers/modelCache.ts`:
- Around line 174-185: The current inFlightRefresh dedupes only by provider
(inFlightRefresh.get(provider)), which lets different authenticated sessions
share the same in-flight promise for the auth-scoped "zoo-gateway" provider;
change the dedupe key to include an auth-scoped identifier when shouldSkipCache
is true. Concretely, compute a cacheKey = shouldSkipCache ?
`${provider}:${authIdOrToken}` : provider (use the request's existing
auth/session identifier already available to getModels — e.g., sessionId,
userId, or authToken), then replace uses of
inFlightRefresh.get(provider)/set(provider, promise)/delete(provider) with
inFlightRefresh.get(cacheKey)/set(cacheKey,...)/delete(cacheKey). Apply this
same change at both places noted (the existing block you saw and the 237-239
block) so in-flight promises for "zoo-gateway" are scoped per-auth.

---

Nitpick comments:
In `@src/api/providers/zoo-gateway.ts`:
- Around line 61-70: The code recreates the OpenAI client via (this as
any).client = new OpenAI(...) which bypasses types and double-instantiates the
client; instead extend RouterProvider to accept a defaultHeaders (or
openAiOptions) parameter and pass enrichmentHeaders and options.zooSessionToken
into the parent constructor so the existing client instance is created with the
correct headers; remove the unsafe cast and the new OpenAI(...) call in
ZooGateway (or the class containing this.client) and update the parent
constructor signature (RouterProvider) to merge DEFAULT_HEADERS,
enrichmentHeaders, and options.openAiHeaders when constructing the OpenAI
client.
- Around line 109-111: When building the request payload in zoo-gateway.ts
(around the object using convertToolsForOpenAI), only include tool-related keys
when metadata?.tools is present: call convertToolsForOpenAI(metadata.tools) and,
if that returns a non-empty array, set tools, tool_choice (from
metadata?.tool_choice) and parallel_tool_calls (from metadata?.parallelToolCalls
?? true); otherwise omit tool_choice and parallel_tool_calls entirely. Update
the payload construction logic to conditionally add these properties instead of
unconditionally setting tool_choice and parallel_tool_calls when tools are
undefined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a8899a1-e2c0-42e5-8da6-017407719daa

📥 Commits

Reviewing files that changed from the base of the PR and between dacc2d4 and f776483.

📒 Files selected for processing (9)
  • packages/types/src/provider-settings.ts
  • packages/types/src/providers/index.ts
  • packages/types/src/providers/zoo-gateway.ts
  • src/api/index.ts
  • src/api/providers/fetchers/modelCache.ts
  • src/api/providers/fetchers/zoo-gateway.ts
  • src/api/providers/index.ts
  • src/api/providers/zoo-gateway.ts
  • src/shared/api.ts

Comment thread src/api/providers/fetchers/zoo-gateway.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 91.57509% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/api/providers/zoo-gateway.ts 91.59% 9 Missing and 1 partial ⚠️
src/api/providers/fetchers/modelCache.ts 73.33% 7 Missing and 1 partial ⚠️
src/api/providers/fetchers/zoo-gateway.ts 95.23% 2 Missing and 1 partial ⚠️
src/api/index.ts 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-types-core branch from f776483 to dfb1eed Compare May 27, 2026 14:23
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/api/providers/__tests__/zoo-gateway.spec.ts (1)

183-197: ⚡ Quick win

Consider fully consuming the stream in assertion tests.

The test only calls .next() once to trigger the request, but doesn't consume the full stream. This pattern appears in several tests (lines 186, 205, 218, 235). While it's sufficient to verify the request shape, fully consuming the stream would also verify that:

  • The stream completes without errors
  • No unexpected chunks are emitted
  • Cleanup/finalization logic runs correctly

Consider using a pattern like:

const chunks = []
for await (const chunk of handler.createMessage(...)) {
  chunks.push(chunk)
}
expect(mockCreate).toHaveBeenCalledWith(...)

This is a recommended improvement for more robust test coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/zoo-gateway.spec.ts` around lines 183 - 197, The
tests call ZooGatewayHandler.createMessage(...) and only invoke .next() which
doesn't fully consume the async iterable; update the test cases (the spec that
uses new ZooGatewayHandler(mockOptions) and mockCreate) to fully iterate the
returned async generator (e.g., for-await ... push chunks into an array) so the
stream is drained and completes before assertions; then assert mockCreate was
called with the expected headers and optionally assert the collected chunks/that
iteration completes without throwing to ensure cleanup/finalization runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/api/providers/__tests__/zoo-gateway.spec.ts`:
- Around line 183-197: The tests call ZooGatewayHandler.createMessage(...) and
only invoke .next() which doesn't fully consume the async iterable; update the
test cases (the spec that uses new ZooGatewayHandler(mockOptions) and
mockCreate) to fully iterate the returned async generator (e.g., for-await ...
push chunks into an array) so the stream is drained and completes before
assertions; then assert mockCreate was called with the expected headers and
optionally assert the collected chunks/that iteration completes without throwing
to ensure cleanup/finalization runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ac3b4bc2-5058-4caf-8700-abd30d236ee0

📥 Commits

Reviewing files that changed from the base of the PR and between 4df54a0 and df5be1d.

📒 Files selected for processing (1)
  • src/api/providers/__tests__/zoo-gateway.spec.ts

@proyectoauraorg
Copy link
Copy Markdown
Contributor

Conflict Resolution Patch

Hi @JamesRobert20 — I've prepared a rebased version of this PR against current main (4d71e5f) that resolves the merge conflicts caused by #319 (opencode-go).

Branch: proyectoauraorg/Zoo-Code:feat/zoo-gateway-types-core-rebased

What was done

  • Rebased all 6 commits onto current main
  • Resolved adjacent insertion conflicts in 8 files by preserving both opencode-go and zoo-gateway entries
  • Added proper opencode-go case in useSelectedModel.ts and modelCache.ts (from main)
  • Added opencode-go to webviewMessageHandler.ts router models mock

Verification

  • tsc --noEmit — zero errors (src + webview-ui)
  • ✅ 39 zoo-gateway tests pass (19 handler + 20 validate)
  • turbo check-types — all 11 packages pass
  • ✅ Lint passes (eslint webview-ui)
  • ✅ Pre-commit + pre-push hooks pass

How to apply

You can cherry-pick the resolution or rebase your branch onto this one:

git fetch proyectoauraorg feat/zoo-gateway-types-core-rebased
git rebase proyectoauraorg/feat/zoo-gateway-types-core-rebased

Happy to help with any further adjustments!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

James Mtendamema and others added 6 commits May 28, 2026 20:50
Co-authored-by: Cursor <cursoragent@cursor.com>
… fetch

- Stop reassigning RouterProvider.client; thread Zoo enrichment headers
  through openAiHeaders so a single OpenAI client is used.
- Replace npm_package_version (never populated at extension runtime)
  with Package.version from the shared package shim.
- Default the model list to [] on a structurally broken response so we
  log and recover instead of crashing on response.data.data being
  undefined.
- Bypass inFlightRefresh de-duplication for zoo-gateway: a refresh
  triggered after sign-out/sign-in must not return the previous user's
  in-flight response.
- Add fetcher unit tests covering auth header, timeout, error
  redaction, and bad-response handling.

Co-authored-by: Cursor <cursoragent@cursor.com>
…RouterName stays exhaustive

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Cover constructor auth guard, base URL resolution, streaming, task/mode headers, temperature, cache breakpoints, tool calls, and completePrompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
The downstream stack (settings-ui) calls getCachedZooCodeToken and
clearZooCodeToken from the auth handler. CI on stacked PRs merges base
into head so this spec runs against the cached-token-aware handler;
expand the auth module mock so the auth guard test exercises the real
throw path instead of vitest's missing-mock-export error.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-types-core branch from 6f2b189 to 2ae4393 Compare May 29, 2026 02:55
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants